개요
쿠버네티스 클러스터 설치를 Ansible을 사용하여 완전 자동으로 구축하는 방법에 대해서 기록해보려고 합니다.
수동으로 한 단계씩 설치하다보면 실수도 잦고, 여러 노드에 동일한 작업을 반복하는 게 여간 번거로운 일이 아니죠. 😅
이 글에서는 기존의 수동 설치 과정(Swap 비활성화, CRI 설치, kubeadm 초기화 등)을 어떻게 Ansible 플레이북으로 옮기는지,
각 작업의 의미는 무엇인지, 그리고 단계별 검증은 어떻게 하는지 A to Z로 알려드리겠습니다.
이번 게시글을 통해 Ansible로 쿠버네티스 클러스터를 설치하는 과정을 익히고,
이를 발판 삼아 kubespray
라는 프로젝트를 사용하여 더 쉽고 간편하게 쿠버네티스 클러스터를 설치하는 방법에 대해서 추후 다뤄볼 예정입니다.
✔️ 사전 준비 사항
본격적으로 시작하기 전에, 아래 환경이 준비되어 있어야 합니다.
- 1. Ansible 제어 노드: 플레이북을 실행할 머신입니다. Ansible이 설치되어 있어야 합니다.
- 2. K8s 클러스터 노드: 최소 2대 이상의 서버 (마스터 1대, 워커 1대 이상). (이 가이드에서는 Ubuntu 22.04 LTS를 기준으로 합니다.)
- 3. SSH 연결: 제어 노드에서 모든 K8s 노드로 비밀번호 없이 SSH 접속(키 기반 인증)이 가능해야 합니다.
- 4. Sudo 권한: 모든 K8s 노드에서 sudo 권한을 가진 계정이 필요합니다.
✔️ 앤서블 서버의 /etc/hosts 업데이트
호스트명 기반으로 k8s 서버 및 노드 IP 주소를 매핑하겠습니다.
echo "192.168.219.130 k8s-master" | sudo tee -a /etc/hosts >&/dev/null
echo "192.168.219.131 k8s-node1" | sudo tee -a /etc/hosts >&/dev/null
echo "192.168.219.132 k8s-node2" | sudo tee -a /etc/hosts >&/dev/null
📁 프로젝트 구조 및 인벤토리 파일 설정
먼저, 체계적인 관리를 위해 프로젝트 폴더와 인벤토리 파일을 생성해주겠습니다.
mkdir ansible-k8s
cd ansible-k8s
touch inventory k8s-install.yml
inventory
파일은 우리가 관리할 서버들의 목록이죠? 마스터 노드와 워커 노드를 그룹으로 나누어 관리하면 훨씬 편리합니다.
[master]
# 마스터 노드의 IP 주소 또는 호스트 이름을 입력하세요.
k8s-master ansible_host=k8s-master ansible_user=k8s-master
[nodes]
# 워커 노드의 IP 주소 또는 호스트 이름을 입력하세요.
k8s-node1 ansible_host=k8s-node1 ansible_user=k8s-node1
k8s-node2 ansible_host=k8s-node2 ansible_user=k8s-node2
[k8s_cluster:children]
master
nodes
ansible_user
에는 각 노드에 접속할 사용자 이름을 지정해주시면 됩니다./etc/hosts
파일에 호스트명과 IP를 매핑해줬다면ansible_host
부분은 삭제하셔도 무방합니다.
Inventory 통신 확인
인벤토리에 작성된 호스트와 앤서블 서버간 정상적으로 통신이 이루어지는지 테스트해봅시다.
ansible -i inventory.ini k8s_cluster -m ping
k8s-node1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
k8s-node2 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
k8s-master | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
📖 Ansible 플레이북 작성
참고
플레이북은 모두 Ubuntu 22.04 쿠버네티스 클러스터 구축하기에서 작성했었던 클러스터 구축 순서를 기반으로 작성하였습니다.
이제 본격적으로 플레이북을 작성해보겠습니다.
전체 과정을 3개의 Play 로 나누어 진행할겁니다.
- 1. Play 1: 모든 노드 (마스터, 워커) 공통 설정
- 2. Play 2: 마스터 노드 초기화 및 설정
- 3. Play 3: 워커 노드 클러스터 조인
Play 1: Master & Node 공통 설정
파일 이름: k8s-master-node-install.yml
- name: Play 1 - K8s 클러스터 공통 설정
hosts: k8s_cluster # inventory에 정의된 k8s_cluster 그룹 실행
become: true # 모든 작업 sudo 권한으로 실행
vars:
K8S_VERSION: "v1.30"
tasks:
# --- 1. Swap 비활성화 ---
- name: "Swap 비활성화 (즉시 적용)"
command: swapoff -a
when: ansible_swaptotal_mb > 0 # 스왑이 이미 있을 때만 실행
- name: "fstab에서 swap 설정 제거하기 (재부팅 후에도 설정 유지하기 위해)"
lineinfile:
path: /etc/fstab
regexp: '.*swap.*'
state: absent # 해당 라인 찾아서 삭제
# --- 2. 커널 모듈 및 파라미터 설정 ---
- name: "필요한 커널 모듈 로드 (overlay, br_netfilter)"
modprobe:
name: "{{ item }}"
state: present
loop:
- overlay
- br_netfilter
# overlay는 컨테이너 파일 시스템을 위해, br_netfilter는 브릿지 네트워크 트래픽을 iptables에서 처리하기 위해 필요합니다.
- name: "쿠버네티스를 위한 sysctl 설정"
sysctl:
name: "{{ item.key }}"
value: "{{ item.value }}"
sysctl_set: yes
state: present
reload: yes
loop:
- { key: 'net.bridge.bridge-nf-call-iptables', value: '1' }
- { key: 'net.ipv4.ip_forward', value: '1' }
- { key: 'net.bridge.bridge-nf-call-ip6tables', value: '1' }
# iptables가 브릿지 네트워크를 통과하는 트래픽을 볼 수 있도록 설정합니다. (CNI 동작에 필수)
# --- [테스트 1] 커널 모듈 및 sysctl 검증 ---
- name: "✅ [테스트 1-1] br_netfilter 모듈 검증"
shell: lsmod | grep br_netfilter
register: is_br_netfilter
failed_when: is_br_netfilter.rc != 0
- name: "✅ [테스트 1-2] overlay 모듈 검증"
shell: lsmod | grep overlay
register: is_overlay
failed_when: is_overlay.rc != 0
- name: "✅ [테스트 1-3] sysctl 검증"
shell: |
sysctl net.bridge.bridge-nf-call-iptables net.bridge.bridge-nf-call-ip6tables net.ipv4.ip_forward
register: is_sysctl
failed_when:
- is_sysctl.stdout.find('net.bridge.bridge-nf-call-iptables = 1') == -1
- is_sysctl.stdout.find('net.bridge.bridge-nf-call-ip6tables = 1') == -1
- is_sysctl.stdout.find('net.ipv4.ip_forward = 1') == -1
# --- 3. CRI 설치 (Containerd) ---
- name: "Docker GPG 키 추가"
apt_key:
url: https://download.docker.com/linux/ubuntu/gpg
state: present
- name: "Docker APT 저장소 추가"
apt_repository:
repo: "deb [arch=amd64] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable"
state: present
- name: "Containerd 설치"
apt:
name: containerd.io
state: present
- name: "Containerd 설정 파일 디렉터리 생성"
file:
path: /etc/containerd
state: directory
- name: "Containerd 기본 설정 파일 생성 및 SystemdCgroup 활성화"
shell: |
containerd config default > /etc/containerd/config.toml
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
# 설명: 쿠버네티스의 kubelet은 Systemd로 cgroup을 관리하므로, 컨테이너 런타임도 동일하게 맞춰줘야 합니다. (매우 중요!)
- name: "Containerd 서비스 재시작 및 활성화"
systemd:
name: containerd
state: restarted
enabled: yes
# --- [테스트 2] Containerd 상태 확인 ---
- name: "✅ [테스트 2] Containerd 서비스 상태 확인"
command: systemctl is-active containerd
register: containerd_status
changed_when: false
- debug:
var: containerd_status.stdout
# 실행 결과가 "active"이면 성공입니다.
# --- 4. 쿠버네티스 구성 요소 설치 (kubelet, kubeadm, kubectl) ---
- name: Update apt package index
ansible.builtin.apt:
update_cache: yes
- name: "필수 패키지 설치"
ansible.builtin.apt:
name:
- apt-transport-https
- ca-certificates
- curl
- gpg
state: present
- name: "apt keyrings 폴더 생성"
ansible.builtin.file:
path: /etc/apt/keyrings
state: directory
mode: '0755'
- name: "Kubernetes GPG 키 추가"
ansible.builtin.shell: "curl -fsSL https://pkgs.k8s.io/core:/stable:/{{ K8S_VERSION }}/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg"
args:
creates: /etc/apt/keyrings/kubernetes-apt-keyring.gpg # 파일이 이미 있으면 실행 안 함
- name: "Kubernetes APT 저장소 추가"
apt_repository:
repo: "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/{{ K8S_VERSION }}/deb/ /"
state: present
filename: kubernetes
- name: Update apt package index again
ansible.builtin.apt:
update_cache: yes
- name: "kubelet, kubeadm, kubectl 설치"
apt:
name: ['kubelet', 'kubeadm', 'kubectl']
state: present
update_cache: yes
- name: "Kube 패키지 버전 고정 (자동 업데이트 방지)"
dpkg_selections:
name: "{{ item }}"
selection: hold
loop:
- kubelet
- kubeadm
- kubectl
# 설명: 클러스터의 안정성을 위해 쿠버네티스 컴포넌트 버전이 예기치 않게 변경되는 것을 막습니다.
- name: "kubelet 서비스 시작"
ansible.builtin.systemd:
name: kubelet
enabled: yes
state: started
플레이북 실행하기
ansible-playbook -i inventory.ini k8s_cluster -K
구간 테스트
1. 스왑 설정
2. 네트워크 설정
3. Containerd 설치
4. kubelet, kubeadm, kubectl 설치
Play 2: 마스터 노드 설정
이제 컨트롤 플레인을 담당할 마스터 노드를 초기화하겠습니다.
파일 이름: master.yml
- name: Play 2 - K8s 마스터 노드 설정
hosts: master
become: yes
tasks:
# --- 1. 컨트롤 플레인 초기화 ---
- name: "kubeadm으로 클러스터 초기화"
command: "kubeadm init --pod-network-cidr=192.168.0.0/16"
args:
creates: /etc/kubernetes/admin.conf # 이 파일이 이미 있다면, 초기화가 완료된 것으로 간주하고 이 작업을 건너뜁니다.
register: kubeadm_init_result
# 설명: --pod-network-cidr는 파드(Pod)들이 사용할 내부 IP 대역입니다. CNI 플러그인(여기서는 Flannel/Calico 기준)과 일치해야 합니다.
- name: "초기화 결과 출력 (Join 명령어 확인용)"
debug:
var: kubeadm_init_result.stdout_lines
# --- 2. kubectl 사용을 위한 환경 설정 ---
- name: "kubectl 설정을 위한 .kube 디렉터리 생성"
file:
path: "/home/{{ ansible_user }}/.kube" # ubuntu 사용자의 홈 디렉터리
state: directory
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
- name: "마스터 노드의 admin.conf 파일을 사용자의 .kube/config로 복사"
copy:
src: /etc/kubernetes/admin.conf
dest: "/home/{{ ansible_user }}/.kube/config"
remote_src: yes # 원격지(마스터 노드)에 있는 파일을 복사합니다.
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
# --- 3. CNI(Container Network Interface) 설치 (Calico) ---
- name: "네트워크 플러그인(Calico) 설치"
become: false # kubectl은 일반 사용자 권한으로 실행
command: "kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.0/manifests/calico.yaml"
args:
chdir: "/home/{{ ansible_user }}" # kubectl이 config 파일을 찾을 수 있도록 홈 디렉터리에서 실행
# 설명: 파드 간의 통신을 가능하게 하는 CNI를 설치합니다. Calico는 강력한 네트워크 정책 기능을 제공합니다.
# --- [테스트 3] 마스터 노드 상태 확인 ---
- name: "✅ [테스트] 1분 대기 후 노드 상태 확인 (CNI Pod 준비 시간)"
pause:
minutes: 1
- name: "✅ [테스트] kubectl get nodes 실행"
become: false
command: "kubectl get nodes"
register: kubectl_get_nodes
changed_when: false
- debug:
var: kubectl_get_nodes.stdout
# 실행 결과에서 마스터 노드의 STATUS가 'Ready'로 표시되면 성공입니다.
Play 3: 워커 노드 클러스터 조인
클러스터 조인 부분은 수동으로 진행하겠습니다 ^^
Play 2를 실행하면 다음과 같이 kubeadm join
이 나올겁니다.
그 명령을 노드에 각각 접속하여 조인시켜주시면 됩니다.